package org.gavin.core.advice;

import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.gavin.core.annotation.LogRecord;
import org.gavin.core.attribute.LogRecordConfig;
import org.gavin.core.constant.CommonConst;
import org.gavin.core.utils.IpUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * LogRecordAdvice
 *
 * @author grl
 * @date 2024/1/10
 */
@Slf4j
@Aspect
@Order(100)
@Component
@RequiredArgsConstructor
public class LogRecordAdvice {

    private final LogRecordConfig recordConfig;

    private final AtomicLong atomicLong = new AtomicLong(0);

    private static final String ATOMIC_COUNT = "atomicValue";

    /**
     * 切入点
     */
    @Pointcut("@annotation(org.gavin.core.annotation.LogRecord)" +
            " && (@target(org.springframework.web.bind.annotation.RestController)" +
            " ||   @target(org.springframework.stereotype.Controller))")
    public void pointCut() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint 切点
     */
    @Before(value = "pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        if (!recordConfig.getEnable()) {
            return;
        }
        try {
            // 开始打印请求日志
            ServletRequestAttributes attributes = getServletRequestAttributes();
            if (attributes == null) {
                log.info("非web请求不记录日志");
                return;
            }
            Long count = getAtomicValue();
            HttpServletRequest request = attributes.getRequest();
            String methodDescription = getAspectLogDescription(joinPoint);
            Enumeration<String> headerNames = request.getHeaderNames();
            Map<String, String> headerMap = new HashMap(16);
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                headerMap.put(name, request.getHeader(name));
            }
            request.setAttribute(ATOMIC_COUNT,count);
            // 打印请求相关参数
            log.info("==========================================打印请求 {} 相关日志开始 ==========================================",count);
            // 打印请求 url
            log.info("URL           {}           : {}", count, request.getRequestURL().toString());
            // 打印描述信息
            log.info("Description   {}           : {}", count,methodDescription);
            // 打印 Http method
            log.info("HTTP Method   {}           : {}", count,request.getMethod());
            // 打印调用 controller 的全路径以及执行方法
            log.info("Class Method  {}           : {}.{}",count, joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            // 打印请求的 IP
            log.info("IP            {}           : {}", count, IpUtils.getIpAddr(request));
            log.info("Content-Type  {}           : {}", count,request.getContentType());
            log.info("headers       {}           : {}", count,headerMap);
            // 打印请求入参
            log.info("Request Args  {}           : {}", count,getParamsAll(joinPoint));
        } catch (Exception e) {
            e.printStackTrace();
            if (joinPoint != null && joinPoint.getTarget() != null && joinPoint.getTarget().getClass() != null) {
                log.info("日志记录错误 {}", joinPoint.getTarget().getClass(), e);
            } else {
                log.info("日志记录错误 获取数据失败: ", e);
            }
        }
    }

    private ServletRequestAttributes getServletRequestAttributes() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes;
    }

    /**
     * 环绕 : 此方法是出参打印，
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        try {
            if (!recordConfig.getEnable()) {
                return proceed;
            }
            // 开始打印请求日志
            printContentType();
            Long count = getCurrentAtomicValue();
            // 打印出参
            log.info("response     {}            : {}", count,JSONObject.toJSONString(proceed));
            // 执行耗时
            log.info("spend time   {}            : {} ms",count, System.currentTimeMillis() - startTime);
            log.info("==========================================打印请求 {} 相关日志结束 ==========================================",count);
        } catch (Exception e) {
            log.info("doAround日志拦截错误==============", e);
        }
        return proceed;
    }

    private void printContentType() {
        ServletRequestAttributes attributes = getServletRequestAttributes();
        if (attributes != null) {
            String contentType = attributes.getResponse().getContentType();
            if(StringUtils.isBlank(contentType)){
                attributes.getResponse().setContentType(CommonConst.CONTENT_TYPE_JSON);
                contentType = CommonConst.CONTENT_TYPE_JSON;
            }
            Long count = getCurrentAtomicValue();
            log.info("response  {}   Content-Type: {}",count,contentType);
        }
    }

    /**
     * 异常方法
     *
     * @param point
     * @param exception
     */
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(JoinPoint point, Exception exception) {
        try {
            String methodName = point.getSignature().getDeclaringTypeName() + " " + point.getSignature().getName();
            List<Object> args = Arrays.asList(point.getArgs());
            log.info("异常方法名为： {} ,参数为： {}  异常信息：", methodName, args, exception);
        } catch (Exception e) {
            log.info("日志拦截错误 @AfterThrowing 错误 ", e);
        }
    }

    /**
     * 获取切面注解的描述
     *
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public String getAspectLogDescription(JoinPoint joinPoint) {
        Method proxyMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        LogRecord logRecord = proxyMethod.getAnnotation(LogRecord.class);
        if (logRecord == null) {
            logRecord = proxyMethod.getDeclaringClass().getAnnotation(LogRecord.class);
        }
        String description = null;
        if (logRecord != null) {
            description = logRecord.description();
        }
        return description;
    }

    /**
     * 打印参数值
     *
     * @param joinPoint
     * @return 缺点:不知道 参数名 与 参数值 的对应关系
     */
    private String getParams(JoinPoint joinPoint) {
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                Object arg = joinPoint.getArgs()[i];
                if ((arg instanceof HttpServletResponse) || (arg instanceof HttpServletRequest) || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[]) || (arg instanceof MultipartConfigElement)) {
                    continue;
                }
                try {
                    if (params.length() == 0) {
                        params += JSONObject.toJSONString(joinPoint.getArgs()[i]);
                    } else {
                        params = params + "," + JSONObject.toJSONString(joinPoint.getArgs()[i]);
                    }
                } catch (Exception e1) {
                    log.error(e1.getMessage());
                }
            }
        }
        return params;
    }


    /**
     * 同时拿到 参数名=参数值
     *
     * @param joinPoint
     * @return
     */
    public String getParamsAll(JoinPoint joinPoint) {
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            // 参数名称
            String[] names = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                Object arg = joinPoint.getArgs()[i];
                if ((arg instanceof HttpServletResponse) || (arg instanceof HttpServletRequest) || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[]) || (arg instanceof MultipartConfigElement)) {
                    continue;
                }
                try {
                    String name = null;
                    if (names != null && names.length > i) {
                        name = names[i] + "=";
                    }
                    if (params.length() == 0) {
                        params += ("" + name + JSONObject.toJSONString(joinPoint.getArgs()[i]));
                    } else {
                        params += ("," + name + JSONObject.toJSONString(joinPoint.getArgs()[i]));
                    }
                } catch (Exception e1) {
                    log.error(e1.getMessage());
                }
            }
        }
        return params;
    }

    private Long getAtomicValue(){
        long andIncrement = atomicLong.getAndIncrement();
        if(andIncrement >= Long.MAX_VALUE - 1){
            atomicLong.setRelease(0);
        }
        return andIncrement;
    }

    private Long getCurrentAtomicValue(){
        ServletRequestAttributes attributes = getServletRequestAttributes();
        if (attributes != null) {
            return (Long)attributes.getRequest().getAttribute(ATOMIC_COUNT);
        }
        return null;
    }
}
